package com.zmzncs.lmtc.common.filter;

import com.alibaba.fastjson.JSON;
import com.zmzncs.lmtc.common.util.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.TeeOutputStream;
import org.slf4j.MDC;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 如果包含header参数，不做输出
 * 顺便 输出对应url 请求体，响应体，耗时
 */
@Configuration
@WebFilter(filterName ="logFilter", urlPatterns = "/*")
@Order(value = 1)
@Slf4j
public class LogFilter implements Filter {

    //  请求队列
    private static List<String> requestCatch = new ArrayList();
    //  客户id + 请求路径
    private String key = "";

    @Override
    public void init(FilterConfig filterConfig) {
        log.debug("过滤器-日志-初始化");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        long requestTime = System.currentTimeMillis();
        String uri = request.getRequestURI();
        String contextPath = request.getContextPath();
        String url = uri.substring(contextPath.length());

        //  日志追踪
        MDC.put("requestId", UUID.randomUUID().toString().replace("-", ""));

        //  静态资源 跳过
        if (url.contains(".")) {
            filterChain.doFilter(request, response);
            return;
        }

        //  输出请求体
        String requestBody = "";
        String requestContentType = request.getHeader(HttpHeaders.CONTENT_TYPE);

        //  普通表单提交
        if (request.getMethod().equals("GET") || requestContentType == null || requestContentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED_VALUE)){
            requestBody = toJson(request.getParameterMap());
        //  xml json
        } else if (requestContentType.startsWith(MediaType.APPLICATION_JSON_VALUE) || requestContentType.startsWith(MediaType.APPLICATION_XML_VALUE)){
            requestBody = getRequestBody(request);
            final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody.getBytes(StandardCharsets.UTF_8));
            request = new HttpServletRequestWrapper(request) {
                @Override
                public ServletInputStream getInputStream() {
                    return new ByteArrayServletInputStream(byteArrayInputStream);
                }
            };
        //  文件表单提交
        } else if (requestContentType.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)){
            requestBody = getFormParam(request);
        }

        final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        response = new HttpServletResponseWrapper(response) {
            @Override
            public ServletOutputStream getOutputStream() throws IOException {
                return new TeeServletOutputStream(super.getOutputStream(), byteArrayOutputStream);
            }
        };

        //  去掉字符串中的换行符、回车、制表符，将连续多个空格替换成一个空格
        Pattern requestPattern = Pattern.compile("\t|\r|\n");
        Matcher requestMatcher = requestPattern.matcher(requestBody);
        requestBody = requestMatcher.replaceAll("").replaceAll(" +", " ");
        log.info("[【请求地址】：{}] [【PARAM】：{}] [【METHOD】：{}] [【CONTENT_TYPE】：{}] [请求真实IP【{}】] [【TOKEN】：{}]", url, requestBody, request.getMethod(), requestContentType, HttpUtil.getIpAddress(request), request.getHeader("token"));

        //  执行下一个过滤器
        filterChain.doFilter(request, response);

        //  打印请求日志
        long costTime = System.currentTimeMillis() - requestTime;
        String responseBody = "";
        //  暂定只有json 输出响应体
        String responseContentType = response.getHeader(HttpHeaders.CONTENT_TYPE);
        if (responseContentType != null && responseContentType.startsWith(MediaType.APPLICATION_JSON_VALUE)){
            responseBody = byteArrayOutputStream.toString();
        }

        //  去掉字符串中的换行符、回车、制表符，将连续多个空格替换成一个空格
        Pattern responsePattern = Pattern.compile("\t|\r|\n");
        Matcher responseMatcher = responsePattern.matcher(responseBody);
        responseBody = responseMatcher.replaceAll("").replaceAll(" +", " ");
        //  响应数据截取2000之内的内容，防止控制台输出太多没有信息
        log.info("[【响应数据】：{}] [【耗时】：{}ms]", responseBody.length() > 2000 ? responseBody.substring(0, 2000) + "...【由于响应数据过程，只响应2000字符】" : responseBody, costTime);

        MDC.remove("requestId");
    }

    @Override
    public void destroy() {
        log.debug("过滤器-日志-销毁");
    }

    private String getRequestBody(HttpServletRequest request) {
        int contentLength = request.getContentLength();
        if(contentLength <= 0){
            return "";
        }
        try {
            return IOUtils.toString(request.getReader());
        } catch (IOException e) {
            log.error("获取请求体失败", e);
            return "";
        }
    }

    private String getFormParam(HttpServletRequest request) {
        MultipartResolver resolver = new StandardServletMultipartResolver();
        MultipartHttpServletRequest mRequest = resolver.resolveMultipart(request);

        Map<String,Object> param = new HashMap<>();
        Map<String,String[]> parameterMap = mRequest.getParameterMap();
        if (!parameterMap.isEmpty()){
            param.putAll(parameterMap);
        }
        Map<String, MultipartFile> fileMap = mRequest.getFileMap();
        if(!fileMap.isEmpty()){
            for (Map.Entry<String, MultipartFile> fileEntry : fileMap.entrySet()) {
                MultipartFile file = fileEntry.getValue();
                param.put(fileEntry.getKey(), file.getOriginalFilename()+ "(" + file.getSize()+" byte)");
            }
        }
        return toJson(param);
    }

    private static String toJson(Object object){
        return JSON.toJSONStringWithDateFormat(object, "yyyy-MM-dd HH:mm:ss");
    }

    /**
     * 输入流
     */
    private class ByteArrayServletInputStream extends ServletInputStream {
        private ByteArrayInputStream byteArrayInputStream;

        public ByteArrayServletInputStream(ByteArrayInputStream byteArrayInputStream) {
            this.byteArrayInputStream = byteArrayInputStream;
        }

        @Override
        public int read() {
            return byteArrayInputStream.read();
        }

        @Override
        public boolean isFinished() {
            return false;
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setReadListener(ReadListener readListener) {

        }
    }

    /**
     * 输出流
     */
    private class TeeServletOutputStream extends ServletOutputStream {
        private final TeeOutputStream teeOutputStream;

        public TeeServletOutputStream(OutputStream one, OutputStream two) {
            this.teeOutputStream = new TeeOutputStream(one, two);
        }

        @Override
        public void write(byte[] b) throws IOException {
            this.teeOutputStream.write(b);
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            this.teeOutputStream.write(b, off, len);
        }

        @Override
        public void write(int b) throws IOException {
            this.teeOutputStream.write(b);
        }

        @Override
        public void flush() throws IOException {
            super.flush();
            this.teeOutputStream.flush();
        }

        @Override
        public void close() throws IOException {
            super.close();
            this.teeOutputStream.close();
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setWriteListener(WriteListener writeListener) {

        }
    }

}

